﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using DocumentFormat.OpenXml.Wordprocessing;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;

namespace MyPublisher.Composition.XWord
{
    class WordprocessingWorker : IDisposable
    {
        private string _sourceFile;
        private string _outputFile;
        private string _templateFile;
        private WordprocessingDocument _inMemoryDocument;
        private MemoryStream _inMemoryStream;

        /// <summary>
        /// Returns True if the document has been opened.
        /// </summary>
        public bool IsOpen
        {
            get { return (_inMemoryDocument != null); }
        }

        /// <summary>
        /// Simple constructor
        /// </summary>
        public WordprocessingWorker()
        {
        }

        #region IDisposable Members

        /// <summary>
        /// This class wraps objects that implement IDisposable, so we better comply
        /// to ensure their resources get released when ours do.
        /// </summary>
        public void Dispose()
        {
            Close();
        }

        #endregion

        /// <summary>
        /// Opens the specified Document file.
        /// This loads it in memory for later modification.
        /// </summary>
        /// <param name="fileName"></param>
        public void Open(string fileName)
        {
            //if the document is already Open well close it first to free up resources.
            if (IsOpen)
            {
                Close();
            }

            _sourceFile = fileName;

            //populate a MemoryStream with byte array of the Document
            byte[] sourceBytes = File.ReadAllBytes(_sourceFile);
            _inMemoryStream = new MemoryStream();
            _inMemoryStream.Write(sourceBytes, 0, (int)sourceBytes.Length);

            //Create the in-memory Document from the stream for modification
            _inMemoryDocument = WordprocessingDocument.Open(_inMemoryStream, true);
        }

        /// <summary>
        /// Validate the in-memory Document. Throws an exception if it is invalid.
        /// </summary>
        public void Validate()
        {
            if (!IsOpen)
            {
                throw new DocumentNotOpenException("The object must be Open before calling Validate()");
            }

            if (_inMemoryDocument != null)
            {
                _inMemoryDocument.Validate(null);
            }
        }

        /// <summary>
        /// Takes the in-memory Document and saves it to the specified file. Overwriting if necessary.
        /// </summary>
        /// <param name="fileName"></param>
        public void SaveAs(string fileName)
        {
            if (!IsOpen)
            {
                throw new DocumentNotOpenException("The object must be Open before calling SaveAs()");
            }

            _outputFile = fileName;
            if (!string.IsNullOrEmpty(_outputFile))
            {
                //Close the in-memory document to ensure the memory stream is ready for saving
                CloseInMemoryDocument();
                try
                {

                    //Now save the stream to file
                    using (FileStream fileStream = new FileStream(_outputFile, System.IO.FileMode.Create))
                    {
                        _inMemoryStream.WriteTo(fileStream);
                    }

                }
                finally
                {
                    //Now close the memory stream. 
                    //The in-memory document has already been closed above 
                    //and there's no point having one without the other!
                    CloseInMemoryStream();
                }
            }
        }

        /// <summary>
        /// Closes and disposes the in-memory document
        /// </summary>
        private void CloseInMemoryDocument()
        {
            if (_inMemoryDocument != null)
            {
                _inMemoryDocument.Close();
                _inMemoryDocument.Dispose();
                _inMemoryDocument = null;
            }
        }

        /// <summary>
        /// Closes and disposes the memory stream
        /// </summary>
        private void CloseInMemoryStream()
        {
            if (_inMemoryStream != null)
            {
                _inMemoryStream.Close();
                _inMemoryStream.Dispose();
                _inMemoryStream = null;
            }
        }

        public void Close()
        {
            CloseInMemoryDocument();
            CloseInMemoryStream();
        }

        /// <summary>
        /// 
        /// </summary>
        public void ApplyHeaderAndFooter(string templateFile)
        {
            _templateFile = templateFile;
            if (!IsOpen)
            {
                throw new DocumentNotOpenException("The object must be Open before calling ApplyHeaderAndFooter()");
            }

            MainDocumentPart mainPart = _inMemoryDocument.MainDocumentPart;

            if (mainPart == null)
            {
                mainPart = _inMemoryDocument.AddMainDocumentPart();
                mainPart.Document = MakeEmpyDocument();
            }
            else
            {
                // Delete the existing header and Footer parts.
                mainPart.DeleteParts(mainPart.HeaderParts);
                mainPart.DeleteParts(mainPart.FooterParts);
            }

            //Multi-sectioned documents seem to have their SectionProperties hidden away inside paragraphs.
            //Go through all these first, and set a flag if we find any.
            bool paragraphSectionPropertiesFound = false;
            IEnumerable<Paragraph> paragraphElements = mainPart.Document.Body.Elements<Paragraph>().AsEnumerable();
            foreach (Paragraph paragraph in paragraphElements)
            {
                ParagraphProperties paragraphProperties = paragraph.Elements<ParagraphProperties>().LastOrDefault();
                if (paragraphProperties != null)
                {
                    SectionProperties sectionProps = paragraphProperties.Elements<SectionProperties>().LastOrDefault();
                    if (sectionProps != null)
                    {
                        ApplyHeaderToSectionProperties(mainPart, sectionProps);
                        ApplyFooterToSectionProperties(mainPart, sectionProps);
                        paragraphSectionPropertiesFound = true;
                    }
                }
            }

            //documents can also have SectionProperties under the body. Particularly single section Documents.
            SectionProperties bodySectionProperties = mainPart.Document.Body.Elements<SectionProperties>().LastOrDefault();

            //if no SectionProperties were found in paragraphs, we will populate the SectionProperties found in the Body instead
            if (paragraphSectionPropertiesFound)
            {
                //SectionProperties were set in the paragraph(s) which should be sufficient, 
                //We'll just remove any header/footer references we find in the Body SectionProperties
                if (bodySectionProperties != null)
                {
                    bodySectionProperties.RemoveAllChildren<HeaderReference>();
                    bodySectionProperties.RemoveAllChildren<FooterReference>();
                }
            }
            else
            {
                if (bodySectionProperties == null)
                {
                    //not there? create it
                    bodySectionProperties = new SectionProperties();
                    ApplyHeaderToSectionProperties(mainPart, bodySectionProperties);
                    ApplyFooterToSectionProperties(mainPart, bodySectionProperties);
                    mainPart.Document.Body.Append(bodySectionProperties);
                }
                else
                {
                    ApplyHeaderToSectionProperties(mainPart, bodySectionProperties);
                    ApplyFooterToSectionProperties(mainPart, bodySectionProperties);
                }
            }

            mainPart.Document.Save();
        }

        private void ApplyHeaderToSectionProperties(MainDocumentPart mainPart, SectionProperties sectionProps)
        {
            // Create a new header part and grab its id for the header reference.
            HeaderPart headerPart = mainPart.AddNewPart<HeaderPart>();
            string rId = mainPart.GetIdOfPart(headerPart);

            //create our Header reference
            HeaderReference headerRef = new HeaderReference();
            headerRef.Id = rId;

            sectionProps.RemoveAllChildren<HeaderReference>();
            sectionProps.Append(headerRef);

            //Now populate the header contents
            //headerPart.Header = MakeHeader();
            WordprocessingDocument wordDoc1 = WordprocessingDocument.Open(_templateFile, false);
            foreach (HeaderPart item in wordDoc1.MainDocumentPart.HeaderParts.ToArray())
            {
                if (item.Header.InnerText.Length > 0)
                {
                    headerPart.FeedData(item.GetStream());
                    break;
                }
            }
            if (headerPart.Header == null)
            {
                headerPart.FeedData(wordDoc1.MainDocumentPart.HeaderParts.ToArray()[0].GetStream());
            }
            headerPart.Header.Save();
        }

        private void ApplyFooterToSectionProperties(MainDocumentPart mainPart, SectionProperties sectionProps)
        {
            // Create a new Footer part and grab its id for the Footer reference.
            FooterPart footerPart = mainPart.AddNewPart<FooterPart>();
            string rId = mainPart.GetIdOfPart(footerPart);

            //create our Footer reference
            FooterReference footerRef = new FooterReference();
            footerRef.Id = rId;

            sectionProps.RemoveAllChildren<FooterReference>();
            sectionProps.Append(footerRef);

            //Now populate the Footer contents
            //footerPart.Footer = MakeFooter();
            //footerPart.Footer.Save();

            WordprocessingDocument wordDoc1 = WordprocessingDocument.Open(_templateFile, false);
            foreach (FooterPart item in wordDoc1.MainDocumentPart.FooterParts.ToArray())
	        {
        		if (item.Footer.InnerText.Length > 0)
        		{
                    footerPart.FeedData(item.GetStream());
                    break;
        		}
	        }
            if (footerPart.Footer == null)
            {
                footerPart.FeedData(wordDoc1.MainDocumentPart.FooterParts.ToArray()[0].GetStream());
            }
            footerPart.Footer.Save();
        }




        public Document MakeEmpyDocument()
        {
            Document doc = new Document();

            Body body = new Body();
            Paragraph paragraph = new Paragraph();
            Run run = new Run();
            Text text = new Text("");
            run.Append(text);
            paragraph.Append(run);
            body.Append(paragraph);
            doc.Append(body);

            return doc;
        }

        //public Header MakeHeader()
        //{
        //    Header headerold = new Header();
        //    Paragraph paragraph = new Paragraph();
        //    Run run = new Run();
        //    Text text = new Text();
        //    text.Text = "Simple header";
        //    run.Append(text);
        //    paragraph.Append(run);
        //    headerold.Append(paragraph);
        //    string xml = headerold.OuterXml;


        //    string fromDocument1 = @"c:\header.docx";
        //    WordprocessingDocument wordDoc1 = WordprocessingDocument.Open(fromDocument1, false);
        //    Header header1 = wordDoc1.MainDocumentPart.HeaderParts.ToArray()[0].Header;

        //    Header header = new Header(header1.OuterXml);
        //    return header;
        //}

        //public Footer MakeFooter()
        //{
        //    Footer footer = new Footer();

        //    ParagraphProperties paragraphProperties = new ParagraphProperties(
        //                    new ParagraphStyleId() { Val = "Footer" },
        //                    new Tabs(
        //                        new TabStop() { Val = TabStopValues.Clear, Position = 4320 },
        //                        new TabStop() { Val = TabStopValues.Clear, Position = 8640 },
        //                        new TabStop() { Val = TabStopValues.Center, Position = 4820 },
        //                        new TabStop() { Val = TabStopValues.Right, Position = 9639 }));

        //    Paragraph paragraph = new Paragraph(

        //                paragraphProperties,
        //                new Run(
        //                    new FieldChar() { FieldCharType = FieldCharValues.Begin }),
        //                new Run(
        //                    new FieldCode(" TITLE   \\* MERGEFORMAT ") { Space = "preserve" }
        //                ),
        //                new Run(
        //                    new FieldChar() { FieldCharType = FieldCharValues.End }),

        //                new Run(
        //                    new PositionalTab() { Alignment = AbsolutePositionTabAlignmentValues.Center, RelativeTo = AbsolutePositionTabPositioningBaseValues.Margin, Leader = AbsolutePositionTabLeaderCharValues.None }
        //                ),
        //                new Run(
        //                    new PositionalTab() { Alignment = AbsolutePositionTabAlignmentValues.Right, RelativeTo = AbsolutePositionTabPositioningBaseValues.Margin, Leader = AbsolutePositionTabLeaderCharValues.None }
        //                ),
        //                new Run(
        //                    new Text("Page ") { Space = "preserve" }
        //                ),
        //                new SimpleField(
        //                    new Run(
        //                        new RunProperties(
        //                            new NoProof()),
        //                        new Text("1")
        //                    )
        //                ) { Instruction = " PAGE   \\* MERGEFORMAT " }
        //            );

        //    footer.Append(paragraph);

        //    return footer;
        //}
    }
}
